﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Graphics;

using Form = System.Windows.Forms.Form;

namespace Cobalt.Content.Pipeline.Graphics.Illumination
{
	partial class IlluminationEngine
	{

		private MeshContent mesh;
		private List<Surface> surfaces = new List<Surface>();
		private List<LightContent> lights = new List<LightContent>();
		private Set<LitMaterialContent> materials = new Set<LitMaterialContent>();
		private Dictionary<object, Lightmap> lightmaps = new Dictionary<object,Lightmap>();
		private bool meshDirty;

		private static readonly string NormalName = VertexChannelNames.Normal( 0 );
		private static readonly string TangentName = VertexChannelNames.Tangent( 0 );
		private static readonly string BinormalName = VertexChannelNames.Binormal( 0 );
		private static readonly string TexCoordName = VertexChannelNames.TextureCoordinate( 0 );
		private static readonly string LitCoordName = VertexChannelNames.TextureCoordinate( 1 );

		private const int DefaultLightmapLayer = 0;
		private const int Rnm0LightmapLayer = 1;
		private const int Rnm1LightmapLayer = 2;
		private const int Rnm2LightmapLayer = 3;
		private const int NumLightmapLayers = 1;

		private const string TargetLightmapKey = "AssignedLightmap";
		public static readonly object DefaultLightmapKey = new object();

		/// <summary>
		/// A function to convert <see cref="GeometryContent"/> instances to arbitrary
		/// lightmap keys. If this is <c>null</c> all geometry is assumed to belong to one lightmap.
		/// </summary>
		/// <remarks>
		/// If the converter returns <c>null</c>, the given geometry will not be lightmapped.
		/// </remarks>
		public Converter<GeometryContent, object> GeometryToLightmapMapping { get; set; }

		private LitMaterialContent ConvertMaterial( MaterialContent srcMaterial )
		{
			if( srcMaterial == null )
				//buh?
				return null;

			return MaterialMapping != null ? MaterialMapping( srcMaterial ) : srcMaterial as LitMaterialContent;
		}

		/// <summary>
		/// A function to convert general <see cref="MaterialContent"/> instances to their
		/// equivalent <see cref="LitMaterialContent"/> object. A <c>null</c> converter
		/// is equivalent to <c>material => material as LitMaterialContent</c>.
		/// </summary>
		/// <remarks>
		/// If the converter returns <c>null</c>, all geometry that uses it will be ignored.
		/// </remarks>
		public Converter<MaterialContent, LitMaterialContent> MaterialMapping { get; set; }

		private object GetLightmapKey( GeometryContent srcGeometry )
		{
			if( srcGeometry == null )
				//buh?
				return null;

			return GeometryToLightmapMapping != null ? GeometryToLightmapMapping( srcGeometry ) : DefaultLightmapKey;
		}

		private Lightmap CreateNewLightmap()
		{
			return new Lightmap( lightmapResolution, lightmapResolution, samples, NumLightmapLayers );
		}

		private void RequireMesh()
		{
			if( meshDirty )
			{
				bool surfacesLoaded = surfaces.Count != 0 &&
					surfaces[0].HasLoadedGraphics;

				if( surfacesLoaded )
					DisposeSurfaceGraphics();

				MeshHelper.MergeDuplicatePositions( mesh, 0 );

				MeshHelper.CalculateTangentFrames( mesh, TexCoordName, TangentName, BinormalName );

				MeshHelper.MergeDuplicateVertices( mesh );
				MeshHelper.OptimizeForCache( mesh );

				if( surfacesLoaded )
					InitializeSurfaceGraphics();

				meshDirty = false;
			}
		}

		public void ClearGeometry()
		{
			DisposeSurfaceGraphics();
			surfaces.Clear();

			lights.Clear();
			materials.Clear();
			lightmaps.Clear();

			mesh = null;
			meshDirty = false;
		}

		#region Hierarchy
		public void AddGeometry( NodeContent geometry )
		{
			if( mesh == null )
			{
				mesh = new MeshContent();
				meshDirty = true;
			}

			AddNodeData( mesh, geometry );
		}

		private void AddNodeData( MeshContent dest, NodeContent node )
		{
			MeshContent nodeMesh = node as MeshContent;
			if( nodeMesh != null )
			{
				AddMeshData( dest, nodeMesh );
				meshDirty = true;
			}

			LightContent nodeLight = node as LightContent;
			if( nodeLight != null )
			{
				lights.Add( nodeLight );
			}

			foreach( NodeContent child in node.Children )
			{
				AddNodeData( dest, child );
			}
		}

		#region Mesh
		private void AddMeshData( MeshContent dest, MeshContent inMesh )
		{
			Matrix transform = inMesh.AbsoluteTransform;
			Matrix invTranspTransform = Matrix.Transpose( Matrix.Invert( transform ) );
			bool isMirrorTransform = Helpers.IsMirror( transform );

			//add vertex positions
			int baseVertex = dest.Positions.Count;
			for( int i = 0; i < inMesh.Positions.Count; i++ )
			{
				Vector3 v = inMesh.Positions[i];
				Vector3.Transform( ref v, ref transform, out v );
				dest.Positions.Add( v );
			}

			foreach( var inGeom in inMesh.Geometry )
			{
				LitMaterialContent litMat = ConvertMaterial( inGeom.Material );
				if( litMat == null )
					//skip anything we don't have a propper material for
					continue;

				if( !inGeom.Vertices.Channels.Contains( TexCoordName ) ||
					!inGeom.Vertices.Channels.Contains( LitCoordName ) ||
					!inGeom.Vertices.Channels.Contains( NormalName ) )
					//skip stuff that doesn't have the coords we need
					continue;

				GeometryContent outGeom = new GeometryContent();
				dest.Geometry.Add( outGeom );

				object lightmapKey = GetLightmapKey( inGeom );
				if( lightmapKey != null )
				{
					Lightmap lightmap;
					if( !lightmaps.TryGetValue( lightmapKey, out lightmap ) )
					{
						lightmap = CreateNewLightmap();
						lightmaps.Add( lightmapKey, lightmap );
					}

					outGeom.OpaqueData[TargetLightmapKey] = lightmap;
				}

				outGeom.Material = litMat;
				outGeom.Name = inGeom.Name;

				foreach( int inPosIdx in inGeom.Vertices.PositionIndices )
					outGeom.Vertices.Add( baseVertex + inPosIdx );

				outGeom.Indices.AddRange( inGeom.Indices );

				if( isMirrorTransform )
					MeshHelpers.SwapWindingOrder( outGeom );

				foreach( var inChan in inGeom.Vertices.Channels )
				{
					if( inChan.Name == NormalName )
					{
						VertexChannel<Vector3> outChan;
						if( inChan.ElementType != typeof( Vector3 ) )
							outChan = outGeom.Vertices.Channels.Add<Vector3>( inChan.Name, inChan.ReadConvertedContent<Vector3>() );
						else
							outChan = outGeom.Vertices.Channels.Add<Vector3>( inChan.Name, (VertexChannel<Vector3>)inChan );

						for( int i = 0; i < outChan.Count; i++ )
						{
							Vector3 v  = outChan[i];

							Vector3.TransformNormal( ref v, ref invTranspTransform, out v );
							Helpers.SafeNormalize( ref v );

							outChan[i] = v;
						}
					}
					else if( inChan.Name == TexCoordName || inChan.Name == LitCoordName )
					{
						VertexChannel<Vector2> outChan;
						if( inChan.ElementType != typeof( Vector2 ) )
							outChan = outGeom.Vertices.Channels.Add<Vector2>( inChan.Name, inChan.ReadConvertedContent<Vector2>() );
						else
							outChan = outGeom.Vertices.Channels.Add<Vector2>( inChan.Name, (VertexChannel<Vector2>)inChan );
					}
				}

				surfaces.Add( new Surface( outGeom ) );
				materials.Add( litMat );
			}
		}
		#endregion
		#endregion
	}
}
